پرش به مطلب اصلی

استایل کد در تیم ستاره

این گزارش با هدف هماهنگی اعضای دولپر تیم ستاره در رعایت استایل مشابه در تمام کدها و هماهنگی آن‌ها با هم و در نتیجه حفظ یکپارچگی در کل پروژه‌ها و راحتی کار برنامه‌نویسان با کدهای یکدیگر باشد. استایل استاندارد شرکت مایکروسافت (https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions)، هسته‌ی اصلی استاندارد کد در تیم ستاره است. در این گزارش، تعدادی از اصول مهمی که انتظار می‌رود تمام برنامه‌نویسان در کدهای خود رعایت کنند، توضیح داده شده‌اند:


  • از آنجا که مواردی مانند تعداد کاراکتر space، وجود خط جدید در انتهای فایل و تنظیمات دیگر توسط config تعریف شده در فایل editorconfig بررسی و اعمال می‌شود، این موارد در این مستند آورده نشده است. بنابراین لازم است جهت هماهنگی این تنظیمات حتما فایل editorconfig مخصوص تیم ستاره در تمام پروژه‌ها موجود بوده و برنامه‌نویسان به استفاده‌ی مرتب از ترکیب کلیدهای IDE جهت مرتب‌سازی خودکار کُد (مانند «Ctrl-Alt-L یا Cmd-Opt-L در IDEهای شرکت JetBrains مثل WebStorm یا Rider»، «Ctrl-Alt-Enter یا Cmd-Opt-Enter در Visual Studio» و «Alt-Shift-F یا Opt-Shift-F در VS Code») عادت داشته باشند.

  • به صورت کلی نام‌گذاری صحیح بسیار مهم است و تاثیر قابل توجهی در درک بهتر کد، خوانایی برنامه، کاهش باگ‌های احتمالی، و راحتی نگهداری از برنامه دارد. نام باید به وضوح نشاندهنده‌ی عملکرد و خروجی تابع، یا نوع و ماهیت مقداری باشد که یک متغیر نگهداری می‌کند. به عنوان نمونه از نوشتن abbreviationهایی که کاملا متداول و رایج نیستند و ممکن است یک خواننده‌ی کد در فهم آن دچار مشکل شود اجتناب می‌کنیم. از نظر ظاهری نیز نام‌گذاری باید شرایطی داشته باشند.

  • در سمت backend که از C# استفاده می‌شود، باید در نامگذاری کلاس‌ها، استاندارد PascalCase را رعایت کنیم. اعضای private و internal باید ترجیحا readonly بوده و به شکل camelCase نام‌گذاری شوند (با _ شروع گردند و حروف اول کلمات غیر از کلمه‌ی اول بزرگ باشند.). اعضای static باید با s و فیلدهای thread static با t_ شروع شوند. اعضای public با استاندارد PascalCase نامگذاری می‌شوند.

  • در سمت frontend باید استاندارد TypeScript که توسط شرکت گوگل ارایه شده است رعایت گردد. شرح کامل این استاندارد در آدرس https://google.github.io/styleguide/tsguide.html#naming آمده است. اسامی کلاس‌ها، باید PascalCase یا UpperCamelCase باشند. اسامی متغیرها و متدها را با استاندارد upperCamelCase می‌نویسیم. در اسامی ثابت‌ها یا constantها فقط از حروف بزرگ استفاده می‌کنیم.

  • ما به هیچ دلیلی spell checker که در تمام IDEها به صورت default فعال است را غیر فعال نمی‌کنیم و همیشه از اشتباهات Typo دوری می‌کنیم. حتی اگر معنی لغتی را شک داریم در دیکشنری‌های معتبر (Oxford، Longman، یا Cambridge) چک می‌کنیم تا اطمینان پیدا کنیم که معنی نام انتخاب شده دقیقا نشان می‌دهد که آن عضو چیست و چه می‌کند.

  • نام متغیرهای محلی (local variable) یا attributeهایی که تایپ آن‌ها bool است و همچنین نام متدهایی که مقدار bool برمی‌گردانند، باید حتما با Is یا Does شروع شود مانند IsAdmin یا DoesHaveAccess.

  • از نوشتن this. اجتناب می‌کنیم، به جز در مواردی مانند hidden data fields که برای نوشتن آن اجبار وجود دارد (یعنی جایی که یک local variable یا متغیر محلی هم‌نام با اسم یک attribute در کلاس وجود دارد که در نتیجه، نوشتن آن نام موجب دسترسی به آن متغیر محلی شده و دسترسی به آن attribute، بدون استفاده از this قبل از نام متغیر امکانپذیر نیست).

  • همیشه visibility modifier (مانند public و private) را می‌نویسیم، حتی اگر مقدار default (پیش‌فرض) آن باشد.

  • کلا همیشه keywordهای زبان را بر تایپ‌های BCL ترجیح می‌دهیم. مثلا در C# به جای System.String می‌نویسیم string و یا به جای System.Int32 می‌نویسیم int.

  • تمام متدهایی که در subclassها override نخواهند شد را حتما static یا sealed تعریف می‌کنیم.

  • در شرایطی که IDE و کامپایلر نوع یا Data Type را تشخیص می‌دهند، آنها را مستقیما نمی‌نویسیم. مثلا در مورد

ExampleClass secondExample = new ExampleClass();

یکی از دو طرف رو نمی‌نویسیم. یعنی یا به شکل:

var firstExample = new ExampleClass();

که نوع متغیر در سمت چپ خود به خود با توجه به مقدار سمت راست تساوی مشخص می‌شود و یا:

ExampleClass instance2 = new();

که در سمت راست تساوی به صورت خودکار مشخص است که از چه کلاسی باید آبجکت ساخته شود.

  • اگر استفاده از Lambda موجب کاهش خوانایی برنامه نشود، حتما از لامبدا استفاده می‌کنیم.

  • کلا تا حد ممکن از نوشتن کامنت اجتناب می‌کنیم، اما اگر هم واقعا لازم بود کامنت بنویسیم، از /* */ برای نوشتن comment خودداری کرده و به جای آن از // استفاده می‌کنیم. کامنت کردن کُدهای برنامه در هیچ حالتی مجاز نبوده و اگر آن کدها مورد نیاز نیستند باید آن‌ها را حذف کنیم.

  • اگر کدهای قبلی را تغییر دادیم که مثلا یک باگ را برطرف کنیم، درصورتیکه کد قبلی از کدهای خیلی قدیمی است که از قبل تست اتوماتیک ندارد، برای آن تست نمی‌نویسیم. اما اگر کلاس یا متد جدید اضافه کردیم، حتما برای آن باید Unit Test بنویسیم.

  • برای DTO (Data Transfer Object)، تست بنویسیم؟ به آبجکت‌هایی که صرفا برای قرار دادن داده‌ها در آن‌ها و ارسالشان از جایی به جای دیگر استفاده می‌شوند، DTO می‌گوییم. نوشتن unit test برای DTO الزامی نیست، اما می‌توان برای آن یک تست نوشت که یک آبکجت از آن ایجاد نموده و مجددا بخوانیم که مطمئن شویم داده‌ی مورد نظر ما در آبجکت قرار داده شده یا نه.

  • در متدها آیا نتیجه رو به یک var محلی assign کنیم و بلافاصله مقدار اون var رو return کنیم؟ به شکل کلی برای استفاده از متغیر در جایی الزام وجود دارد که نیاز است از یک داده بیش از یک مرتبه استفاده شود. اگرچه با توجه به این توضیح به نظر می‌رسد وجود یک متغیر در چنین مواردی که صورت سوال بالا بیان کرده کاری زاید است، اما به طور کلی هرگاه محاسبه‌ها طولانی هستند به نسبت مقدار طولانی بودنشان، آن را به چند مرحله می‌شکنیم و نتیجه‌ی هر مرحله را در یک متغیر میریزیم تا readability (خوانایی) کدمان افزایش یابد. (دقت کنید که انتخاب مناسب نام‌های متغیرها به تنهایی کمک شایانی به فهم روش کار کُد ما می‌کنند.) در کُد نمونه‌ی زیر ۵ متغیر تعریف شده که بدون آن‌ها هم امکان انجام کل محاسبات در یک فرمول در همان یک خط برنامه که دارای دستور return است وجود می‌داشت، اما وجود آن ۵ خط اضافی با متغیرهای میانی که در آن‌ها تعریف شده، فهم روش محاسبه را بسیار آسانتر می‌کند. همینطور مشاهده می‌کنید که فهم کامل خطی که monthlyPayment محاسبه شده است، همچنان آسان نیست و اینجا باز هم نیاز به متغیر(های) واسطه‌ای بیشتر برای خوانایی بالاتر احساس می‌شود:

double monthlyInterestRate = annualInterestRate / 12;
double monthlyInterestRateFactor = 1 + monthlyInterestRate / 100;
int numberOfMonths = numberOfYear * 12;
double InterestAmount = Math.pow(monthlyInterestRateFactor, numberOfMonths);
double monthlyPayment = loanAmount * monthlyInterestRate / (1 – (1 / InterestAmount));
return monthlyPayment;
  • در کدهای سمت frontend، قسمت‌هایی از کد که fuctionality بوده و مربوط به render شدن صفحه نیستند، را تبدیل به سرویس می‌کنیم.

  • برای if با body (بدنه‌ی) تک خطی آکولاد باز و آکولاد بسته بگذاریم؟ اگر body را روبروی شرط نوشته‌ایم خیر:

if (condition) statement;

اما اگر body را در سطر بعد نوشته‌اید با توجه به احتمال بروز مشکل به واسه کامنت کردن آن body تک خطی مانند

If (condition)
// statement;
otherStatements;

یا افزوده شدن statement به body و فراموش کردن افزودن آکولاد، مانند

if(condition)
statement;
otherNewStatements;

حتما آکولاد را بگذاریم.

  • کجاها از IIF (Inline IF) استفاده می‌کنیم؟ اگر جایی انجام دادن یک کار به شرط وابسته نیست، ولی آرگیومنت آن به شرط وابسته است، باید از IIF استفاده کنیم. مانند:
return (i > 5) ? 5 : i;
  • اگر یک condition (شرط) طولانی داریم، شرط را در یک variable (متغیر) با تایپ bool و با نام مناسب قرار داده و در شرط، آن متغیر را چک کنیم.
bool IsDataAvailable = (MyList is not null && MyList.size() != 0);
if (IsDataAvailable) DoSomething;

در جایی که بیش از دو مرحله شرط وجود دارد (برای انتخاب از بین بیش از سه مقدار مختلف به عنوان آرگیومنت مورد استفاده)، از IIF استفاده نکنیم و اگر دو مرحله است درصورتی از IIF استفاده کنیم که readability (خوانایی) دچار مشکل نشود.

  • شرط استفاده از switch-case؟ در جایی که تعدادی شرط برای انجام تعدادی کار متفاوت داریم که در تمام این شرط‌ها یک statement یا variable (مانند متغیر i در مثال زیر) با مقادیر ثابت مختلف مقایسه می‌شوند، باید از ساختار switch-case استفاده کنیم. ترتیب caseها را دقیقا به ترتیب صعودی مقادیری (valueهایی) که چک می‌کنند بنویسیم. اگر عملی که برای دو یا بیشتر مقدار مختلف از i قرار است انجام شود، یکسان است، ممکن است برای خوانایی بیشتر caseهای مربوطه را در یک خط مقابل هم بنویسید (البته مرتب کننده‌ی اتوماتیک IDE هر case را در یک خط قرار می‌دهد):
switch (i)
{
case 4:
case 5:
body1;
break;
case 6:
body2;
break;
case 7:
body3;
break;
case 8:
body4;
break;
}
  • به صورت کلی داشتن تعداد زیاد حالت در ساختارهای if-else یا switch-case چندان مناسب نیست. در اینگونه شرایطی، معمولا با استفاده از design patternها و یا استفاده از سرویس در سمت frontend، می‌توان کد تمیزتر و بهتری نوشت.

  • اگر در جایی if داریم که دارای قسمت else نیز هست، بهتره شرط رو مثبت بذاریم نه منفی. مثلا به جای

if (!IsSomething) 
{
body1;<br>
}
else
{
body2;
}

بنویسیم:

if (IsSomething) 
{
body2;
}
else
{
body1;
}
  • در مواردی مانند مثال زیر که مواردی وجود دارد که با کاما از یکدیگر جدا می‌شوند، برای سطر آخر هم کاما میگذاریم با وجود اینکه الزامی به قرار دادن آن وجود ندارد. دلیل آن راحتی بیشتر در افزودن مورد جدید، حذف یکی از موارد موجود، و یا جابجا کردن موارد قبلی است. مثلا در کد زیر در IDEهای شرکت JetBrains می‌توان روی سطر آخر رفت و با استفاده از ctrl+d در ویندوز یا cmd+d در لینوکس یک کپی از آن سطر ایجاد و سپس تغییرات لازم را روی آن اعمال کرد.
new MyClass () 
{
A = “a”,
B = “b”,
}